/*  
 *  Copyright 2006 Paul Jack.
 *
 *  This file is part of the Dex compiler suite. 
 *  
 *  Dex is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 2 of the License, or (at your
 *  option) any later version.
 *  
 *  Dex is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 *  Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */
package dex.compiler.interpreter;


import java.util.Collection;
import java.util.List;
import java.util.Map;

import dex.compiler.checker.FunctionSearcher;
import dex.compiler.checker.type.ArrayType;
import dex.compiler.checker.type.IntegerType;
import dex.compiler.checker.type.MemberType;
import dex.compiler.checker.type.Type;
import dex.compiler.interpreter.value.ArrayValue;
import dex.compiler.interpreter.value.BooleanValue;
import dex.compiler.interpreter.value.Defaults;
import dex.compiler.interpreter.value.FunctionValue;
import dex.compiler.interpreter.value.IntegerValue;
import dex.compiler.interpreter.value.MemberValue;
import dex.compiler.interpreter.value.Value;
import dex.compiler.model.definition.ClassDef;
import dex.compiler.model.definition.Constructor;
import dex.compiler.model.definition.Function;
import dex.compiler.model.definition.TypedName;
import dex.compiler.model.expression.ArrayExpression;
import dex.compiler.model.expression.CastExpression;
import dex.compiler.model.expression.DirectCall;
import dex.compiler.model.expression.IdentifierExpression;
import dex.compiler.model.expression.IndirectCall;
import dex.compiler.model.expression.Infix;
import dex.compiler.model.expression.InfixExpression;
import dex.compiler.model.expression.IsExpression;
import dex.compiler.model.expression.LiteralBoolean;
import dex.compiler.model.expression.LiteralFunction;
import dex.compiler.model.expression.LiteralInteger;
import dex.compiler.model.expression.LiteralString;
import dex.compiler.model.expression.MemberExpression;
import dex.compiler.model.expression.NewExpression;
import dex.compiler.model.expression.UnaryExpression;
import dex.compiler.model.expression.VirtualCall;
import dex.compiler.model.program.NameSearcher;
import dex.compiler.model.program.Path;
import dex.compiler.model.program.Program;
import dex.compiler.model.program.Programs;
import dex.compiler.model.type.ArrayTypeNode;
import dex.compiler.model.type.BasicTypeNode;
import dex.compiler.model.type.FunctionTypeNode;


/**
 * Evaluates expressions.
 */
class ExpressionEvaluator extends StatementExecutor {

	
	final protected Program program;


	/**
	 * Constructs a new <code>ExpressionEvaluator</code>.
	 * 
	 * @param program  the program to interpret
	 */
	public ExpressionEvaluator(State state) {
		super(state);
		this.program = state.getProgram();
	}

	
	@Override
	protected void walkInfixExpression(InfixExpression e) {
		Infix infix = e.getOperator();
		walk(e.getLeft());
		
		// Short-circuit && and ||.
		if (infix == Infix.LOGICAL_AND) {
			if (!state.popBoolean()) {
				state.push(BooleanValue.FALSE);
				return;
			}
			walk(e.getRight());
			return;
		} 
		if (infix == Infix.LOGICAL_OR) {
			if (state.popBoolean()) {
				state.push(BooleanValue.TRUE);
				return;
			}
			walk(e.getRight());
			return;
		}
		
		Value left = state.pop();
		walk(e.getRight());
		Value right = state.pop();

		if (left instanceof IntegerValue) {
			state.push(InfixEvaluator.operate(infix, (IntegerValue)left, (IntegerValue)right));
		} else {
			// FIXME: Local arrays and local records are compared-by-value?
			state.push(InfixEvaluator.equality(infix, left, right));
		}		
	}


	public void visitArrayTypeNode(ArrayTypeNode t) {
		// TODO Auto-generated method stub		
	}


	public void visitBasicTypeNode(BasicTypeNode t) {
		// TODO Auto-generated method stub		
	}


	public void visitFunctionTypeNode(FunctionTypeNode t) {
		// TODO Auto-generated method stub		
	}

	

	public void visitArrayExpression(ArrayExpression e) {
		IntegerValue iv = (IntegerValue)state.pop();
		ArrayValue av = (ArrayValue)state.popReference();
		long index = iv.value();
		Value[] array = av.value();
		state.checkBounds(array, index);				
		state.push(array[(int)index]);
	}


	public void visitCallExpression(IndirectCall e) {
		// TODO Auto-generated method stub	
	}


	public void visitCastExpression(CastExpression e) {
		// TODO Auto-generated method stub		
	}


	public void visitDirectCall(DirectCall e) {
		List<Value> values = state.pop(e.getParameters().size());

		Collection<Type> types = Value.types(values);
		FunctionSearcher searcher = new FunctionSearcher(Function.class, manager, e.getName(), types);
		List<Path> found = Programs.search(program, state.context(), searcher);
		Value r = execute(found.get(0), values, null);
		Function f = (Function)found.get(0).getDefinition();
		if (!f.isVoid()) {
			state.push(r);
		}
	}


	public void visitIdentifierExpression(IdentifierExpression e) {
		String id = e.getIdentifier();
		Value r = state.getLocal(id);
		if (r != null) {
			state.push(r);
			return;
		}

		NameSearcher searcher = new NameSearcher(TypedName.class, id);
		List<Path> found = Programs.search(program, state.context(), searcher);
		Path var = found.get(0);
		state.push(state.getGlobal(var.qualified()));
	}


	public void visitInfixExpression(InfixExpression e) {
		// nop; handled by walker
	}


	public void visitIsExpression(IsExpression e) {
		// TODO Auto-generated method stub		
	}


	public void visitLiteralBoolean(LiteralBoolean e) {
		if (e.getValue()) {
			state.push(BooleanValue.TRUE); 
		} else {
			state.push(BooleanValue.FALSE);
		}
	}


	public void visitLiteralFunction(LiteralFunction e) {
		List<Type> types = manager.toTypes(state.context(), e.getParameterTypes());
		FunctionSearcher searcher = new FunctionSearcher(Function.class, manager, e.getName(), types);
		List<Path> found = Programs.search(program, state.context(), searcher);
		state.push(new FunctionValue(manager, found.get(0)));
	}


	public void visitLiteralInteger(LiteralInteger e) {
		int width = IntegerType.getMinimumBitWidth(true, e.getValue());
		state.push(new IntegerValue(true, width, e.getValue()));
	}


	public void visitLiteralString(LiteralString e) {
		// TODO Auto-generated method stub		
	}


	public void visitMemberExpression(MemberExpression e) {
		MemberValue owner = (MemberValue)state.popReference();
		state.push(owner.fields().get(e.getMemberName()));
	}


	
	public void visitNewExpression(NewExpression e) {
		List<Value> params = state.pop(e.getParameters().size());
		Type type = manager.toType(state.context(), e.getType());
		if (type instanceof ArrayType) {
			newArray((ArrayType)type, params);
			return;
		}
		MemberType mt = (MemberType)type;
		if (mt.isRecord()) {
			newRecord(mt, params);
		} else {
			newObject(mt, params);
		}
	}


	private void newRecord(MemberType type, List<Value> params) {
		MemberValue v = new MemberValue(manager, type);
		int i = 0;
		for (Map.Entry<String,Value> e: v.fields().entrySet()) {
			e.setValue(params.get(i));
			i++;
		}
		state.push(v);
	}


	private void newObject(MemberType type, List<Value> params) {
		Collection<Type> types = Value.types(params);
		FunctionSearcher searcher = new FunctionSearcher(Constructor.class, manager, "new", types);
		ClassDef cls = (ClassDef)manager.getDefinition(type).getDefinition();
		List<Path> found = Programs.search(program, state.context(), cls, searcher);
		Value v = execute(found.get(0), params, type);
		state.push(v);
	}
	
	private void newArray(ArrayType type, List<Value> params) {
		// FIXME: Multidimensionals?
		IntegerValue bound = (IntegerValue)params.get(0);
		Value[] value = new Value[(int)bound.value()];
		for (int i = 0; i < value.length; i++) {
			value[i] = Defaults.get(type.getElementType());
		}
		state.push(new ArrayValue(type, value));
	}

	public void visitUnaryExpression(UnaryExpression e) {
		switch (e.getOperator()) {
			case NEGATE: {
				IntegerValue v = (IntegerValue)state.pop();
				int width = v.width();
				if (!v.signed()) {
					width = Math.min(width * 2, 64);
				}
				long value = v.value();
				v = new IntegerValue(true, width, -value);
				state.push(v);
				break;
			}
			case INVERT: {
				IntegerValue v = (IntegerValue)state.pop();
				v = new IntegerValue(v.signed(), v.width(), ~v.value());
				state.push(v);
				break;
			}
			case NOT: {
				boolean v = state.popBoolean();
				state.push(v ? BooleanValue.FALSE : BooleanValue.TRUE);
			}
		}
	}


	public void visitVirtualCall(VirtualCall e) {
		// TODO Auto-generated method stub
	}

}
